/**
  ******************************************************************************
  * File Name          : spiflash.c
  * Description        : This file provides code for the configuration of the spi flash driver.
  ******************************************************************************
  * @attention
  *
  ******************************************************************************
  */
#include "sfbsp.h"
#include <stdint.h>

/* Includes ------------------------------------------------------------------*/
#define PAGE_SIZE           256u
#define SECTOR_SIZE         (256u * 16u)
#define BLOCK32_SIZE        (256u * 16u * 8u)
#define BLOCK64_SIZE        (256u * 16u * 16u)

#define CMD_WRITE_ENABLE    0x06u
#define CMD_READ_SR1        0x05u
#define CMD_READ_DATA       0x03u
#define CMD_PROGRAM_PAGE    0x02u
#define CMD_ERASE_SECTOR    0x20u
#define CMD_ERASE_BLOCK32   0x52u
#define CMD_ERASE_BLOCK64   0xD8u
#define CMD_ERASE_CHIP      0xC7u

/* Status Registers 1 (s7~s0) */
#define SR1_BUSY            0x01u
#define SR1_WEL             0x02u
#define SR1_BP0             0x04u
#define SR1_BP1             0x08u
#define SR1_BP2             0x10u
#define SR1_TB              0x20u
#define SR1_SEC             0x40u
#define SR1_SRP             0x80u

#define SFLASH_LED
#define DUMMY               0x00u
#define FLASHSPI            SPI1
#define wp_low()            SFLASH_WP_LOW()
#define wp_high()           SFLASH_WP_HIGH()
#define cs_low()            SFLASH_CS_LOW()
#define cs_high()           SFLASH_CS_HIGH()
#define led_read(x)         LED0(x)
#define led_progran(x)      LED1(x)

/******** inline spi driver  trade space for time and sacrificed some portability ********/
static inline uint8_t spi_transbyte(uint8_t txc)
{	
	while( (SPI_STAT(FLASHSPI) & SPI_FLAG_TBE) == RESET )
	{
	}
	
	SPI_DATA(FLASHSPI) = txc;
	
	while( (SPI_STAT(FLASHSPI) & SPI_FLAG_RBNE) == RESET )
	{
	}
	
	return (uint8_t)SPI_DATA(FLASHSPI);
}

/* gd32f450 200Mhz 实际测量 79~85ns */
static inline void delay_50ns(void)
{
	__asm("nop");__asm("nop");__asm("nop");
	__asm("nop");__asm("nop");__asm("nop");
	__asm("nop");__asm("nop");__asm("nop");
	__asm("nop");__asm("nop");__asm("nop");
}

static inline void waiting_for_idle(void)
{
	uint8_t sr1;
	
	delay_50ns();
	
	cs_low();
	
	spi_transbyte(CMD_READ_SR1);
	
	do
	{
		sr1 = spi_transbyte(DUMMY);
	}
	while( (sr1 & SR1_BUSY) == SET );
	
	cs_high();
	
	delay_50ns();
}

static inline void write_enable(void)
{
	delay_50ns();
	
	cs_low();
	
	spi_transbyte(CMD_WRITE_ENABLE);
	
	cs_high();
	
	delay_50ns();
}

/* Programming multiples of sectors */
static inline void page_program(uint32_t addr, uint8_t *wbuf, uint32_t length)
{
	uint32_t i;
	
	waiting_for_idle();
	
	write_enable();
	
	cs_low();
	
	spi_transbyte(CMD_PROGRAM_PAGE);
	spi_transbyte((addr & 0x00ff0000u) >> 16u);
	spi_transbyte((addr & 0x0000ff00u) >> 8u);
	spi_transbyte(addr & 0x000000ffu);
	
	for(i=0; i<length; i++)
	{
		spi_transbyte(wbuf[i]);
	}
	
	cs_high();
	
	waiting_for_idle();
}

void sFlash_ReadChipID(uint8_t *rbuf)
{
	cs_low();
	
	spi_transbyte(0x9fu);
	
	rbuf[0] = spi_transbyte(DUMMY);
	rbuf[1] = spi_transbyte(DUMMY);
	rbuf[2] = spi_transbyte(DUMMY);
	
	cs_high();
}

void sFlash_AnywhereRead(uint32_t addr, uint8_t *rbuf, uint32_t length)
{
	uint32_t i;
	
	waiting_for_idle();
	
	cs_low();
	
	spi_transbyte(CMD_READ_DATA);
	spi_transbyte((addr & 0x00ff0000u) >> 16u);
	spi_transbyte((addr & 0x0000ff00u) >> 8u);
	spi_transbyte(addr & 0x000000ffu);
	
	for(i=0; i<length; i++)
	{
		rbuf[i] = spi_transbyte(DUMMY);
	}
	
	cs_high();

	delay_50ns();
}

void sFlash_SectorErase(uint32_t addr)
{
	waiting_for_idle();
	
	write_enable();
	
	cs_low();
	
	spi_transbyte(CMD_ERASE_SECTOR);
	spi_transbyte((addr & 0x00ff0000u) >> 16u);
	spi_transbyte((addr & 0x0000ff00u) >> 8u);
	spi_transbyte(addr & 0x000000ffu);
	
	cs_high();
	
	waiting_for_idle();
}

void sFlash_Block32Erase(uint32_t addr)
{
	waiting_for_idle();
	
	write_enable();
	
	cs_low();
	
	spi_transbyte(CMD_ERASE_BLOCK32);
	spi_transbyte((addr & 0x00ff0000u) >> 16u);
	spi_transbyte((addr & 0x0000ff00u) >> 8u);
	spi_transbyte(addr & 0x000000ffu);
	
	cs_high();
	
	waiting_for_idle();
}

void sFlash_Block64Erase(uint32_t addr)
{
	waiting_for_idle();
	
	write_enable();
	
	cs_low();
	
	spi_transbyte(CMD_ERASE_BLOCK64);
	spi_transbyte((addr & 0x00ff0000u) >> 16u);
	spi_transbyte((addr & 0x0000ff00u) >> 8u);
	spi_transbyte(addr & 0x000000ffu);
	
	cs_high();
	
	waiting_for_idle();
}

void sFlash_ChipErase(void)
{	
	waiting_for_idle();
	
	write_enable();
	
	cs_low();
	
	spi_transbyte(CMD_ERASE_CHIP);
	
	cs_high();
	
	waiting_for_idle();
}

void sFlash_PageAlignedProgram(uint32_t addr, uint8_t *wbuf, uint32_t length)
{
	uint32_t num_of_page, num_of_single;
	
	num_of_page = length / PAGE_SIZE;
	
	if(num_of_page == 0)
	{
		page_program(addr, wbuf, length);
	}
	else
	{
		num_of_single = length % PAGE_SIZE;
		
		while(num_of_page--)
		{
			page_program(addr, wbuf, PAGE_SIZE);
			addr +=  PAGE_SIZE;
			wbuf +=  PAGE_SIZE;
		}

		page_program(addr, wbuf, num_of_single);
	}
}

void sFlash_SectorAlignedProgram(uint32_t addr, uint8_t *wbuf, uint32_t length)
{
	uint32_t num_of_page, num_of_single;
	
	if( (addr&0x00000fffu) != 0)
	{
		return;
	}
	
	num_of_page = length / PAGE_SIZE;
	
	if(num_of_page == 0)
	{
		page_program(addr, wbuf, length);
	}
	else
	{
		num_of_single = length % PAGE_SIZE;
		
		while(num_of_page--)
		{
			page_program(addr, wbuf, PAGE_SIZE);
			addr +=  PAGE_SIZE;
			wbuf +=  PAGE_SIZE;
		}

		page_program(addr, wbuf, num_of_single);
	}
}

void sFlash_AnywhereProgram(uint32_t addr, uint8_t *wbuf, uint32_t length)
{
	uint8_t  byte_addr_inpage = 0, byte_num_inpage = 0;

  byte_addr_inpage = addr % PAGE_SIZE;

  if(byte_addr_inpage == 0)
  {
    sFlash_PageAlignedProgram(addr, wbuf, length);
  }
  else
  {
		byte_num_inpage = PAGE_SIZE - byte_addr_inpage;

		page_program(addr, wbuf, byte_num_inpage);
		
		sFlash_PageAlignedProgram(addr + byte_num_inpage, wbuf + byte_num_inpage, length - byte_num_inpage);
  }
}
